/*
 * history.c
 *
 *  Created on: 2022年2月4日
 *      Author: TamGitsun
 */

#include "history.h"
#include "utils.h"
#include "kv_port.h"
#include "configs.h"
#include "fsl_debug_console.h"
#include <tos_kv.h>


#define HISTORY_KEY_LAST_DAY "LAST_DAY"

#define HISTORY_N_SECONDS_EACH_DAY 86400  // 3600 * 24

#define HISTORY_SECOND_VALUE_2_DAY_VALUE(v_) ((v_) / 1000)
#define HISTORY_DAY_VALUE_2_SECOND_VALUE(v_) ((v_) * 1000)

static int g_all_day_data[HISTORY_N_SECONDS_EACH_DAY] = {0};  // x10000
static int g_all_data[3650] = {0};  // x100
static day_t g_all_data_index = 0;

static int g_history_inited = 0;
static day_t g_day_time = 0;
static k_mutex_t g_day_mutex;


typedef int (* FuncOnCompressEachPart)(char * buffer, size_t size, void * user_data);
typedef int (* FuncOnDecompressEachPart)(int * values, size_t n_values, void * user_data);


#define SHIFT_BITS 7

/**
 * @brief Compress an integer value by bits operation
 * @note We assert that the size of @p out is larger than or equivalent to `ceil(sizeof(int) * 8.0f / SHIFT_BITS)`
 * 
 * @param value The integer value to be compressed
 * @param out The compressed bytes, we assert that is is not NULL
 * @return int The count of compressed bytes
 */
static int compress_int(int value, unsigned char * out)
{
    if (0 == value)
    {
        out[0] = 0;
        return 1;
    }
    int i = 0;
    for (; value > 0; ++i)
    {
        int part = value & 0x7F;
        value >>= SHIFT_BITS;
        if (value > 0)
        {
            part |= 0x80;
        }
        out[i] = (unsigned char) part;
    }
    return i;
}

/**
 * @brief Decompress an integer value by bits operation
 * 
 * @param in The compressed integer value
 * @param size The size of @p in
 * @return int The decompressed integer value
 */
static int decompress_int(unsigned char * in, size_t size)
{
    int value = 0;
    int n_shifts = 0;
    for (int i = 0; i < size; ++i, n_shifts += SHIFT_BITS)
    {
        int part = in[i];
        if (0 == (part & 0x80))
        {
            value += (part << n_shifts);
            break;
        }
        part &= 0x7F;
        value += (part << n_shifts);
    }
    return value;
}


static int compress(const int * values, size_t n_values, FuncOnCompressEachPart onCompressEachPart, void * user_data)
{
    char part[1024] = {0};
    char * pw = part;
    size_t size = sizeof(part);
    size_t compress_max_size = (sizeof(int) << 3);
    compress_max_size = compress_max_size / SHIFT_BITS + (compress_max_size % SHIFT_BITS ? 1 : 0);
    for (size_t i = 0; i < n_values; ++i)
    {
        int n = compress_int(values[i], pw);
        pw += n;
        if (size < compress_max_size)
        {
            if (!onCompressEachPart(part, sizeof(part) - size, user_data))
            {
                return 0;
            }
            pw = part;
            size = sizeof(part);
        }
    }
    if (pw != part)
    {
        if (!onCompressEachPart(part, sizeof(part) - size, user_data))
        {
            return 0;
        }
    }
    return 1;
}

static int decompress(char * buffer, size_t size, FuncOnDecompressEachPart onDecompressEachPart, void * user_data)
{
    int part[256] = {0};
    size_t n_values = 0;
    char * pr = buffer, * p_cur = buffer;
    for (size_t i = 0; i < size; ++i)
    {
        if (0 == (*p_cur & 0x80))
        {
            ++p_cur;
            continue;
        }
        ++p_cur;
        part[n_values++] = decompress_int(pr, p_cur - pr);
        if (n_values >= (sizeof(part) / sizeof(part[0])))
        {
            if (!onDecompressEachPart(part, n_values, user_data))
            {
                return 0;
            }
            n_values = 0;
        }
        pr = p_cur;
    }
    return pr == p_cur;
}

#undef SHIFT_BITS


void history_init(void)
{
	if (g_history_inited)
	{
		return;
	}
	g_day_time = k_tick_t_2_day_t(get_sys_time());  // Take the first second of a day
	tos_mutex_create(&g_day_mutex);
    g_history_inited = 1;
}

void history_get_day(day_t begin, day_t end, FuncOnHistoryGetEachDay callback)
{
    if (!g_history_inited || !callback || end < begin)
    {
        return;
    }

    if (begin < (g_day_time - g_all_data_index) || end > g_day_time)
    {
    	return;
    }

    day_t n_days = end - begin + 1;
    for (day_t i = 0; i < n_days; ++i)
    {
    	tos_mutex_pend(&g_day_mutex);
    	int data = HISTORY_SECOND_VALUE_2_DAY_VALUE(g_all_data[g_all_data_index - (g_day_time - (i + begin))]);
    	tos_mutex_post(&g_day_mutex);
    	callback(i + begin, data);
    }
}

void history_get_second(k_tick_t begin, k_tick_t end, FuncOnHistoryGetEachSecond callback)
{
    if (!g_history_inited || !callback || end < begin)
    {
        return;
    }

    day_t day_begin = k_tick_t_2_day_t(begin);
    day_t day_end = k_tick_t_2_day_t(end);
    if (day_begin != g_day_time || day_end != g_day_time)
    {
    	return;
    }
    
    k_tick_t begin_offset = k_tick_t_2_day_seconds(begin);

    begin /= 1000;
    end /= 1000;

    k_tick_t n_seconds = end - begin + 1;
    for (k_tick_t i = 0; i < n_seconds; ++i)
    {
    	tos_mutex_pend(&g_day_mutex);
    	int data = g_all_day_data[begin_offset + i];
    	tos_mutex_post(&g_day_mutex);
    	callback(i + begin, data);
    }
}

void history_put(k_tick_t time, int value)
{
    if (!g_history_inited)
    {
        return;
    }
    day_t day = k_tick_t_2_day_t(time);
	tos_mutex_pend(&g_day_mutex);
    do {
		if (g_day_time > day)
		{
			PRINTF("Day steps back!\r\n");
			break;
		}
		if (g_day_time == day)
		{
			g_all_day_data[k_tick_t_2_day_seconds(time)] += value;
            // PRINTF("Push to %d value: %d\r\n", (int) k_tick_t_2_day_seconds(time), value);
			g_all_data[g_all_data_index] += value;
			break;
		}
		++g_all_data_index;
		memset(g_all_day_data, 0, sizeof(g_all_day_data));
		g_all_day_data[k_tick_t_2_day_seconds(time)] += value;
		g_all_data[g_all_data_index] += value;
		++g_day_time;
    } while (0);
	tos_mutex_post(&g_day_mutex);
}

day_t history_get_first_day(void)
{
    return g_day_time - g_all_data_index;
}

void history_process(void)
{
    day_t now = k_tick_t_2_day_t(get_sys_time());
	tos_mutex_pend(&g_day_mutex);
	if (now > g_day_time)
	{
		++g_all_data_index;
		memset(g_all_day_data, 0, sizeof(g_all_day_data));
		++g_day_time;
	}
	tos_mutex_post(&g_day_mutex);
}

void history_deinit(void)
{
    if (!g_history_inited)
    {
        return;
    }
	tos_mutex_destroy(&g_day_mutex);
    memset(g_all_day_data, 0, sizeof(g_all_day_data));
    memset(g_all_data, 0, sizeof(g_all_data));
}

void history_update_simulate_data(void)
{
    random_int_generator_t random_generator;
    random_int_generator(&random_generator,
        HISTORY_DAY_VALUE_2_SECOND_VALUE(100), HISTORY_DAY_VALUE_2_SECOND_VALUE(999));  // 1° ~ 9.99°
    for (int i = 0; i < CONF_N_DAYS_SIMULATE; ++i)
    {
        g_all_data[i] = random_generator.next(&random_generator);
    }
    if (g_all_data_index < CONF_N_DAYS_SIMULATE)
    {
        g_all_data_index = CONF_N_DAYS_SIMULATE;
    }
}
